Лекция посвящена фундаментальным конструкциям C++ и базовым типам Qt.
Цель — систематизировать знания о типах данных, управлении памятью и потоке
выполнения, которые потребуются для сетевого программирования.
Акцент на современных конструкциях C++11 и выше.
Порядок тем: от простых типов к управлению памятью, затем управление потоком,
структуры данных и в конце — Qt-расширения. На лекцию рассчитывайте ~90 минут.
Размеры типов зависят от платформы: int обычно 4 байта, но стандарт
гарантирует лишь int >= 2 байта. Для сетевого кода используйте типы
с фиксированным размером: int32_t, uint16_t из <cstdint>.
float — 4 байта, double — 8 байт. В сетевых приложениях double
предпочтительнее для точных вычислений. bool занимает 1 байт.
const: значение нельзя изменить после инициализации.
constexpr: вычисляется на этапе компиляции — быстрее, но ограничено
константными выражениями. Встраивайте constexpr-функции для
вычисления размеров буферов и таймаутов.
volatile: компилятор НЕ кэширует значение в регистре — важно для
сигналов от оборудования и multi-threaded флагов.
mutable: «легальный обход» const — используется для кэширования,
логирования, подсчёта обращений.
Ключевое отличие enum от enum class: обычный enum «загрязняет»
пространство имён — DISCONNECTED виден везде. enum class требует
указания ProtocolType::TCP, что предотвращает конфликты.
Рекомендуйте студентам всегда использовать enum class.
union — экономит память, но небезопасен. В C++17 появился
std::variant<int, float, std::string> — типобезопасная замена.
Неявное расширение (int -> double) безопасно. Сужение (double -> int)
происходит молча и теряет данные — частый источник багов.
Компилятор может выдать warning; включите -Wconversion.
static_cast — наиболее частый, проверяется компилятором.
reinterpret_cast — побитовое переосмысление, опасно, используйте
только для низкоуровневой работы с буферами (сетевые пакеты).
dynamic_cast — работает только с полиморфными типами (есть virtual),
возвращает nullptr если приведение неверно. Требует RTTI.
const_cast — снятие/добавление const; изменение изначально const
объекта — UB (undefined behavior).
Правило integer promotion: все типы меньше int (char, short, bool)
расширяются до int перед арифметическими операциями.
Иерархия: int -> unsigned int -> long -> unsigned long -> long long ->
float -> double -> long double.
В сетевых протоколах важно учитывать расширение при побитовых
операциях: uint8_t flag = 0x80; auto x = ~flag; // x — int, не uint8_t!
Фигурные скобки {} — «uniform initialization» — предотвращает
сужающее преобразование: int x{3.14}; — ошибка компиляции!
Рекомендуйте {} как стиль по умолчанию.
auto повышает читаемость, но помните: auto x = "hello" — это
const char*, не std::string. Используйте auto с явным типом
инициализатора.
nullptr заменяет NULL и 0 — типобезопасно, неявно не преобразуется
в integer.
Qt-типы имеют неявное совместное использование данных (copy-on-write),
поэтому копирование QString/QVector дешёвое.
QHostAddress — основной класс для работы с IP-адресами в Qt Network.
QHostAddress::Any — слушает на всех интерфейсах (0.0.0.0).
Глобальные переменные — зло: затрудняют отладку, не потокобезопасны,
нарушают инкапсуляцию. Избегайте их, кроме констант.
Пространства имён — лучший способ группировки. Можно вкладывать:
namespace Net { namespace Tcp { ... } }.
В Qt используется D-указатель (Pimpl-идиома) для сокрытия данных.
Правило: объявляйте переменные как можно ближе к месту использования.
C++17 добавил if с инициализатором:
if (auto it = map.find(key); it != map.end()) { ... }
Полезно для ограничения области видимости.
Автоматические переменные живут на стеке — быстро, безопасно.
Деструктор вызывается автоматически при выходе из области видимости.
Это основа идиомы RAII — Resource Acquisition Is Initialization.
Все ресурсы (файлы, сокеты, блокировки) оборачивайте в классы
с деструкторами.
static-переменная в функции: живёт всё время работы программы,
но видна только внутри функции. Потокобезопасная инициализация
в C++11 (magic statics).
static-член класса: один экземпляр на все объекты. Полезен для
подсчёта подключений, пула ресурсов.
Внимание: порядок уничтожения static-объектов в разных единицах
трансляции не определён — возможно static destruction order fiasco.
new/delete — главная источник утечек памяти и ошибок.
Правило: НИКОГДА не используйте raw new/delete в прикладном коде.
unique_ptr — единоличное владение, нулевой overhead.
shared_ptr — разделяемое владение, есть overhead (control block).
make_unique/make_shared — безопаснее (exception safety).
В Qt есть альтернатива: QScopedPointer, QSharedPointer.
if с инициализатором (C++17):
if (auto socket = connect(); socket != nullptr) { ... }
Полезно для ограничения области видимости переменной условия.
Тернарный оператор — выражение, возвращает значение. Не вкладывайте
тернарные операторы друг в друга — нечитаемо.
switch работает только с целыми типами и enum.
Забытый break — классическая ошибка (fall-through).
C++17: [[fallthrough]] атрибут для явного указания намеренного
проваливания. Компиляторы с -Wimplicit-fallthrough предупреждают.
Для строк и сложных типов используйте if-else или std::map.
C++23 добавит std::expected и pattern matching через inspect.
Классический for — когда нужен индекс или сложная логика.
Range-based for — чище и безопаснее, невозможно выйти за границы.
Внимание: for (auto x : container) копирует элементы!
Используйте const auto& для тяжёлых объектов.
Бесконечный for(;;) — идиоматичнее чем while(true).
Классический паттерн для повторных попыток подключения.
В сетевых приложениях обязательно ограничивайте число попыток
и добавляйте задержку (exponential backoff), чтобы не перегрузить
сервер.
do-while гарантирует хотя бы одно выполнение тела.
Часто используется для: меню, валидация ввода, первичная попытка
подключения.
Иногда вместо do-while лучше использовать while(true) + break —
условие выхода становится более читаемым.
break — выход из ближайшего цикла или switch.
continue — переход к следующей итерации.
ВНИМАНИЕ: в C++ НЕТ labeled break (как в Java). Для выхода из
вложенных циклов используйте: флаг, goto (крайне не рекомендуется),
или вынесите внутренний цикл в отдельную функцию с return.
Статические массивы: размер известен на этапе компиляции.
Локальные массивы без инициализации содержат мусор — частый баг.
std::array — обёртка над C-массивом: знает свой размер, поддерживает
range-based for, at() с проверкой границ, нулевой overhead.
В сетевом коде: буферы фиксированного размера для приёма пакетов.
operator[] — без проверки, O(1), но при выходе за границы — UB.
at() — с проверкой, бросает исключение. В production лучше at()
или статический анализ.
UB — худший тип ошибки: программа «работает» пока не сломается
в неожиданный момент. Используйте AddressSanitizer для отладки:
-fsanitize=address -fsanitize=undefined.
new[]/delete[] — забытый delete[] = утечка памяти. Исключение
между new и delete = утечка. Никогда не используйте в современном C++.
std::vector — стандартный динамический массив. Управляет памятью
сам, поддерживает random access, итераторы, range-based for.
reserve() — если знаете размер заранее, избегаете реаллокаций.
В сетевом коде: vector<uint8_t> для буферов приёма/передачи.
C-строки — массивы char, завершающиеся нулевым символом '\0'.
strcpy/strcat — НЕ проверяют размер буфера → buffer overflow,
частая уязвимость в безопасности. Используйте strncpy/strncat
или, лучше, вообще std::string.
В сетевых протоколах C-строки встречаются в API операционной
системы (POSIX sockets). Конвертируйте в std::string как можно
раньше.
std::string — основной строковый тип C++. Управляет памятью
автоматически, поддерживает конкатенацию через +.
Small String Optimization (SSO): короткие строки (~15 символов)
хранятся внутри объекта, без heap-аллокации.
find() возвращает std::string::npos если не найдено.
stoi/stol/stof/stod — преобразование строки в число.
Бросают исключения при ошибке. В Qt: QString::toInt() возвращает
bool через параметр.
QString — Unicode (UTF-16) строка, основа Qt.
Аргументы через .arg() безопаснее sprintf — нет проблем с типами.
toStdString() — конвертация в std::string (UTF-8 в Qt 6).
toLocal8Bit() — в локальной кодировке, может быть некорректно.
Всегда используйте QString::fromUtf8() для явного указания кодировки.
QDebug: qDebug() << qtString1; — удобный вывод для отладки.
Qt 6: QList и QVector объединены — QList теперь реализован как QVector.
QMap — красно-чёрное дерево, O(log n) поиск, отсортированные ключи.
QHash — хеш-таблица, O(1) амортизированный поиск.
Выбор: если нужен порядок ключей — QMap, иначе QHash (быстрее).
Java-style итераторы (QListIterator) устарели — используйте
range-based for или STL-итераторы.
auto — вывод типа компилятором. Улучшает читаемость при сложных
типых: auto it = map.find(key) вместо map<string,int>::iterator.
const auto& — ссылка, избегает копирования. Ключевое слово!
nullptr — типобезопасная замена NULL (который был int 0).
Современный C++ (14/17/20): if constexpr, structured bindings,
concepts, ranges — если интересно, изучите дополнительно.
Резюме: лекция покрыла фундамент C++ и Qt, необходимый для
сетевого программирования. Главное — использовать современные
конструкции и избегать опасных паттернов (raw pointers, C-строки,
глобальные переменные).
Для следующей лекции: будем изучать структуры классов, наследование
и полиморфизм — ключевые механизмы ООП в контексте сетевых приложений.
Вопросы для проверки усвоения материала. Особое внимание — вопросам
4 (RAII/умные указатели) и 8 (нет labeled break — важно!).
Ответы: 1) const — неизменяемость, constexpr — вычисление при
компиляции; 4) unique_ptr/shared_ptr, RAII; 5) в Qt6 — одинаковы;
8) флаг, goto, вынесение в функцию с return.